﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using GrapplingHookGameData;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Factories;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;

namespace GrapplingHookGameData
{
    /// <summary>
    /// Class that represents a level.
    /// Some values are read from the level definition xml file through the content pipeline
    /// and placed in a LevelDefinition object.
    /// </summary>
    public class Level
    {
        private const float WALL_MASS = 1.0f;
        private const float WALL_RESTITUTION_COEFFICIENT = 0.2f;
        private const float WALL_FRICTION_COEFFICIENT = 1.0f;

        private LevelDefinition levelDefinition;
        public LevelDefinition LevelDefinition
        {
            get { return levelDefinition; }
        }
        private SpriteSheet tiles;

        private List<Rectangle> solidWalls;

        //used for drawing
        private Vector2 tileDestination;

        public Level()
        {
            solidWalls = new List<Rectangle>();
        }


        public void LoadContent(string levelName, ContentManager content, GraphicsDevice graphicsDevice, PhysicsSimulator physicsSimulator)
        {
            levelDefinition = content.Load<LevelDefinition>(Constants.LEVEL_DIRECTORY + levelName);
            tiles = new SpriteSheet(Constants.LEVEL_TILE_DIRECTORY +levelDefinition.TileSet);
            tiles.LoadContent(content, Constants.TILE_WIDTH, Constants.TILE_HEIGHT);
            generateSolidWalls();
            addWallsToPhysicsEngine(physicsSimulator);
        }

        private void generateSolidWalls()
        {
            //keep track of blocks already checked
            bool[] checkedBlocks = new bool[levelDefinition.CollisionBlocks.Length];

            for (int y = 0; y < levelDefinition.Height; y++)
            {
                for (int x = 0; x < levelDefinition.Width; x++)
                {
                    //find first block not already checked
                    if (levelDefinition.CollisionBlocks[y * levelDefinition.Width + x] && !checkedBlocks[y * levelDefinition.Width + x])
                    {
                        checkedBlocks[y * levelDefinition.Width + x] = true;
                        int startX = x;
                        int startY = y;
                        int endX = x;
                        int endY = y;
                        //find end of row
                        while (++x < levelDefinition.Width && levelDefinition.CollisionBlocks[y * levelDefinition.Width + x] && !checkedBlocks[y * levelDefinition.Width + x])
                        {
                            checkedBlocks[y * levelDefinition.Width + x] = true;
                            endX++;
                        }
                        //extend block down
                        while (endY < levelDefinition.Height - 1 && isSolidRow(endY + 1, startX, endX, checkedBlocks)) endY++;

                        //add wall
                        solidWalls.Add(new Rectangle(startX, startY, endX - startX+1, endY- startY+1));

                        //start next search on same row after block
                        x = endX;
                    }
                    else
                    {
                        checkedBlocks[y * levelDefinition.Width + x] = true;
                    }

                }
            }
        }

        /// <summary>
        /// Checks if a row is solid, and if so marks it as checked
        /// </summary>
        /// <param name="rowToCheck"></param>
        private bool isSolidRow(int rowToCheck, int fromX, int toX, bool[] checkedBlocks)
        {
            int currentX = fromX;
            while (currentX <= toX)
            {
                if (!levelDefinition.CollisionBlocks[rowToCheck * levelDefinition.Width + currentX++]) return false;
            }
            //a row has been found below, mark it as checked
            while (fromX <= toX)
            {
                checkedBlocks[rowToCheck * levelDefinition.Width + fromX++] = true;
            }
            return true;
        }

        /// <summary>
        /// Take the rectangles that describe the walls and make them into physics objects and add them to the physics engine
        /// </summary>
        /// <param name="physicsSimulator"></param>
        private void addWallsToPhysicsEngine(PhysicsSimulator physicsSimulator)
        {
            foreach (Rectangle wall in solidWalls)
            {
                Body body = BodyFactory.Instance.CreateRectangleBody(physicsSimulator, wall.Width * Constants.TILE_WIDTH, wall.Height * Constants.TILE_HEIGHT, WALL_MASS);
                Geom geom = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator, body, wall.Width * Constants.TILE_WIDTH, wall.Height * Constants.TILE_HEIGHT, Constants.COLLISION_GRID_SIZE);
                body.Position = new Vector2((wall.X * Constants.TILE_WIDTH) + wall.Width * Constants.TILE_WIDTH / 2 - Constants.TILE_WIDTH / 2, wall.Y * Constants.TILE_HEIGHT + wall.Height * Constants.TILE_HEIGHT / 2 - Constants.TILE_HEIGHT / 2);
                body.IsStatic = true;
                geom.RestitutionCoefficient = WALL_RESTITUTION_COEFFICIENT;
                geom.FrictionCoefficient = WALL_FRICTION_COEFFICIENT;
            }
        }

        public virtual void Draw(SpriteBatch spriteBatch)
        {
            for (int y = 0; y < levelDefinition.Height; y++)
            {
                for (int x = 0; x < levelDefinition.Width; x++)
                {
                    int tileIndex = levelDefinition.TileIdices[x + (levelDefinition.Width * y)] - 1;
                    if (tileIndex >= 0)
                    {
                        tileDestination.X = x * Constants.TILE_WIDTH - Constants.TILE_WIDTH / 2;
                        tileDestination.Y = y * Constants.TILE_HEIGHT - Constants.TILE_HEIGHT / 2;

                        spriteBatch.Draw(tiles.Texture, tileDestination, tiles.SourceRectangle(tileIndex), Color.White);
                    }
                }
            }
        }


    }
}
